﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;

namespace Red.Scripts.Project.Generator.Project
{
    public static class XElementExtensions
    {
        public static readonly XNamespace ns = @"http://schemas.microsoft.com/developer/msbuild/2003";

        public static IEnumerable< XElement > DescendantsNS( this XDocument document, string name )
        {
            return document.Descendants( ns + name );
        }

        public static XElement CreateElement( string name )
        {
            return new XElement( ns + name );
        }

        public static XElement CreateElement( string name, object value )
        {
            return new XElement( ns + name, value );
        }
    }

    public class Template
    {
        private List<string> _files = new List<string>();
        private XDocument _contents;

        public List<string> Files => _files;
        public string SourceFile { get; set; } = @"Red.Scripts.redproj";
        public Dictionary<string, IProperty> Properties { get; set; } = new Dictionary<string, IProperty>();

        public bool Load()
        {
            _contents = XDocument.Load( SourceFile );

            if ( _contents != null )
            {
                Log.WriteLine( $"Loaded project template: {SourceFile}" );
                return true;
            }
            else
            {
                Log.WriteErrorLine( $"Could not find project template at location: {SourceFile}" );
                return false;
            }
        }

        public bool Write( string target )
        {
            if ( !AddProjectGuid() )
                return false;

            if ( !AddScriptsProperties() )
                return false;

            if ( File.Exists( target ) )
            {
                File.SetAttributes( target, FileAttributes.Normal );
                File.Delete( target );
            }

            XElement group = FindRedScriptItemGroup();
            WriteFiles( group );

            _contents.Save( target );

            return true;
        }

        private bool AddProjectGuid()
        {
            return AddPropertyGroupElement( "Label", "Globals", "ProjectGuid", Properties[ "ProjectGuid" ].Object );
        }

        private bool AddScriptsProperties()
        {
            XElement propertyGroup = FindPropertyGroupAfterElement( "Import", "Project", @"$(RedScriptsProjectFilesPath)RedScripts.props" );

            if( propertyGroup == null )
            {
                return false;
            }

            Dictionary<string, XElement> propertyGroups = new Dictionary<string, XElement>();

            foreach( var pair in Properties )
            {
                IProperty property = pair.Value;

                if( property.IsSet && property.Name != "ProjectGuid" )
                {
                    XElement parent;

                    if ( string.IsNullOrWhiteSpace( property.Label ) )
                    {
                        parent = propertyGroup;
                    }
                    else
                    {
                        if ( !propertyGroups.TryGetValue( property.Label, out parent ) )
                        {
                            parent = XElementExtensions.CreateElement( "PropertyGroup" );
                            XAttribute attr = new XAttribute( "Label", property.Label );
                            parent.Add( attr );

                            propertyGroup.AddAfterSelf( parent );
                            propertyGroups.Add( property.Label, parent );
                        }
                    }

                    XElement element = XElementExtensions.CreateElement( property.Name, property.Object );
                    parent.Add( element );
                }
            }
            
            return true;
        }

        private XElement FindPropertyGroupAfterElement( string elementName, string attributeName, string attributeValue )
        {
            XElement import = FindElementWithAttributeValue( elementName, attributeName, attributeValue );

            if( import == null )
            {
                Log.WriteErrorLine( $"Could not find '{elementName}' element with attribute '{attributeName}'='{attributeValue}' in project template" );
                return null;
            }
            
            XElement propertyGroup = XElementExtensions.CreateElement( "PropertyGroup" );
            import.AddAfterSelf( propertyGroup );
            return propertyGroup;
        }

        private bool AddPropertyGroupElement( string attributeName, string attributeValue, string elementName, object elementValue )
        {
            XElement propertyGroup = FindElementWithAttributeValue( "PropertyGroup", attributeName, attributeValue );

            if ( propertyGroup == null )
            {
                Log.WriteErrorLine( "Could not find 'Globals' PropertyGroup in project template" );

                return false;
            }
            
            XElement element = XElementExtensions.CreateElement( elementName, elementValue );
            propertyGroup.Add( element );

            return true;
        }

        private XElement FindElementWithAttributeValue( string elementName, string attributeName, string attributeValue )
        {
            IEnumerable<XElement> propertyGroups = _contents.DescendantsNS( elementName );

            foreach ( XElement propertyGroup in propertyGroups )
            {
                XAttribute attr = propertyGroup.Attribute( attributeName );
                if ( attr != null )
                {
                    if ( attr.Value == attributeValue )
                        return propertyGroup;
                }
            }

            return null;
        }

        private bool Replace<T>( string elementName, T value )
        {
            XElement element = _contents.DescendantsNS( elementName ).FirstOrDefault();

            if ( element == null )
            {
                Log.WriteErrorLine( $"Could not find element '{elementName}' in project template" );
                return false;
            }

            element.SetValue( value );
            return true;
        }

        private XElement FindRedScriptItemGroup()
        {
            XElement firstScript = _contents.DescendantsNS( "RedScript" ).FirstOrDefault();
            return firstScript.Parent;
        }

        private void WriteFiles( XElement parent )
        {
            parent.RemoveAll();

            foreach ( string file in Files )
            {
                WriteFile( parent, file );
            }
        }

        private void WriteFile( XElement parent, string file )
        {
            XElement element = XElementExtensions.CreateElement( "RedScript" );
            element.SetAttributeValue( "Include", file );

            parent.Add( element );
        }
    }
}
